#include "../../core/graphic/render_item.h"
#include <d3dx9.h>

static KRenderState mCurrentRS;
static KRenderState mDefaultRS;
static D3DCOLOR g_bgColor = 0;

extern unsigned int g_viewportWidth;
extern unsigned int g_viewportHeight;

static unsigned int primVertMagic[] = {
	1, 2,  // triangle strip
	3, 0,  // triangle list
	1, 1,  // line strip
	2, 0   // line list
};

static D3DPRIMITIVETYPE pirmTypeMap[] = {
	D3DPT_TRIANGLESTRIP,
	D3DPT_TRIANGLELIST,
	D3DPT_LINESTRIP,
	D3DPT_LINELIST
};

extern LPDIRECT3DDEVICE9       g_pd3dDevice;



#define D3DFVF_VERTEX_P (D3DFVF_XYZ)
#define D3DFVF_VERTEX_PC (D3DFVF_XYZ|D3DFVF_DIFFUSE)
#define D3DFVF_VERTEX_PT (D3DFVF_XYZ|D3DFVF_TEX1)

struct D3D_HW_BUFFER
{
	LPDIRECT3DVERTEXBUFFER9 pVB;
	unsigned int vertexStride;
	unsigned int primCnt;
	D3DPRIMITIVETYPE primType;
	DWORD vertexFVF;
	D3D_HW_BUFFER() {pVB = NULL; vertexStride = 0;}
};

void ApplyWorldMatrix(const KDrawLocal& local);
void ApplyBlendState(const KRenderState& rs);
void ApplyConstColor(unsigned int color);

void KDrawManager::SetRenderState(const KRenderState& rs)
{
	bool currentHasTex = mCurrentRS.mTexture ? true : false;
	bool itemHasTex = rs.mTexture ? true : false;

	if (mCurrentRS.mTexture != rs.mTexture) {
		if (rs.mTexture)
			rs.mTexture->Bind();
		else
			g_pd3dDevice->SetTexture(0, NULL);
	}

	if (mCurrentRS.mModulateColor != rs.mModulateColor)
		ApplyConstColor(rs.mModulateColor);

	if (mCurrentRS.mBlendMode != rs.mBlendMode)
		ApplyBlendState(rs);

	mCurrentRS = rs;
}


void ApplyWorldMatrix(const KDrawLocal& local) 
{
	D3DXMATRIX transMat;
	D3DXMatrixIdentity(&transMat);
	transMat(0,0) = local.rot.mCosTheta;
	transMat(0,1) = local.rot.mSinTheta;
	transMat(3,0) = local.x;

	transMat(1,0) = -local.rot.mSinTheta;
	transMat(1,1) = local.rot.mCosTheta;
	transMat(3,1) = local.y;

	g_pd3dDevice->SetTransform( D3DTS_WORLD, &transMat );
}



void ApplyBlendState(const KRenderState& rs)
{
	switch (rs.mBlendMode) {
case KRenderState::eDisableBlend:
	g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
	break;
case KRenderState::eAlphaBlend:
	g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
	break;
case KRenderState::eAddBlend:
	g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
	g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
	break;
	}

}

void ApplyConstColor(unsigned int color) 
{
	unsigned int d3d_color = color;
	BYTE* pColor = (BYTE*)&d3d_color;
	std::swap(pColor[0], pColor[2]);
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_CONSTANT, d3d_color);
}


D3D_HW_BUFFER* CreateHWBufferImpl(const BYTE* pSrcData, KPrimType type, unsigned vertStride, unsigned int vertexCnt, DWORD fvf)
{
	D3D_HW_BUFFER* pBuffer = new D3D_HW_BUFFER;

	pBuffer->vertexStride = vertStride;
	pBuffer->vertexFVF = fvf;
	pBuffer->primType = pirmTypeMap[type];
	pBuffer->primCnt = vertexCnt / primVertMagic[type*2] - primVertMagic[type*2 + 1];;
	HRESULT hr = g_pd3dDevice->CreateVertexBuffer( vertexCnt * vertStride,
		0, fvf,
		D3DPOOL_MANAGED, &pBuffer->pVB, NULL );
	if (FAILED(hr))
		return NULL;

	// Fill the vertex buffer
	BYTE* pDstData = NULL;
	if( FAILED( pBuffer->pVB->Lock( 0, 0, ( void** )&pDstData, 0 ) ) ) {
		if (pBuffer->pVB)
			pBuffer->pVB->Release();
		pBuffer->pVB = NULL;
		return NULL;
	}
	
	for (unsigned int vi = 0; vi < vertexCnt; ++vi) {

		float* pPos = (float*)pDstData;
		float* pSrc = (float*)pSrcData;
		pPos[0] = pSrc[0];
		pPos[1] = pSrc[1];
		pPos[2] = 0;
		pDstData += (3*sizeof(float));
		pSrcData += (3*sizeof(float));

		if (fvf & D3DFVF_DIFFUSE) {
			DWORD* pPos = (DWORD*)pDstData;
			DWORD* pSrc = (DWORD*)pSrcData;
			pPos[0] = pSrc[0];
			
			pDstData += sizeof(DWORD);
			pSrcData += sizeof(DWORD);

		}

		if (fvf & D3DFVF_TEX1) {
			float* pPos = (float*)pDstData;
			float* pSrc = (float*)pSrcData;
			pPos[0] = pSrc[0];
			pPos[1] = pSrc[1];
			pDstData += (2*sizeof(float));
			pSrcData += (2*sizeof(float));
		}
	}

	pBuffer->pVB->Unlock();

	return pBuffer;
}

void KDrawManager::DeleteHWBuffer(HW_BufferPtr pBuffer)
{
	D3D_HW_BUFFER* pD3DBuffer = (D3D_HW_BUFFER*)pBuffer;
	if (pD3DBuffer->pVB) pD3DBuffer->pVB->Release();
	delete pD3DBuffer;
}

HW_BufferPtr KDrawManager::CreateHWBuffer(const VERTEX_P*  pData, KPrimType type, unsigned int vertCnt)
{
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_CONSTANT );
	return CreateHWBufferImpl((const BYTE*)pData, type, sizeof(VERTEX_P), vertCnt, D3DFVF_VERTEX_P);
}

HW_BufferPtr KDrawManager::CreateHWBuffer(const VERTEX_PC*  pData, KPrimType type, unsigned int vertCnt)
{
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
	return CreateHWBufferImpl((const BYTE*)pData, type, sizeof(VERTEX_PC), vertCnt, D3DFVF_VERTEX_PC);
}

HW_BufferPtr KDrawManager::CreateHWBuffer(const VERTEX_PT*  pData, KPrimType type, unsigned int vertCnt)
{
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_CONSTANT );
	return CreateHWBufferImpl((const BYTE*)pData, type, sizeof(VERTEX_PT), vertCnt, D3DFVF_VERTEX_PT);
}

void KDrawManager::Draw_HWBuf(const KDrawLocal& local, HW_BufferPtr pBuffer)
{
	ApplyWorldMatrix(local);
	D3D_HW_BUFFER* pD3DBuffer = (D3D_HW_BUFFER*)pBuffer;
	if (!pD3DBuffer->pVB) return;

	if (pD3DBuffer->vertexFVF & D3DFVF_DIFFUSE) {
		g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 );
		g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 );
		g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
		g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
	}

	g_pd3dDevice->SetStreamSource(0, pD3DBuffer->pVB, 0, pD3DBuffer->vertexStride);
	g_pd3dDevice->SetFVF(pD3DBuffer->vertexFVF);
	g_pd3dDevice->DrawPrimitive(pD3DBuffer->primType, 0, pD3DBuffer->primCnt);

	if (pD3DBuffer->vertexFVF & D3DFVF_DIFFUSE) {
		g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_CONSTANT );
		g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_CONSTANT );
		g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
		g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
	}
}

void KDrawManager::Begin()
{
	
	mCurrentRS = mDefaultRS;

	g_pd3dDevice->SetRenderState(D3DRS_ALPHAREF, (DWORD)0x00000001);
	g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); 
	g_pd3dDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);

	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_CONSTANT );

	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_CONSTANT );

	ApplyBlendState(mCurrentRS);
	ApplyConstColor(mCurrentRS.mModulateColor);

	// Clear the back buffer
	g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, g_bgColor, 1.0f, 0 );

	g_pd3dDevice->BeginScene();
	
}

void KDrawManager::End()
{
	g_pd3dDevice->EndScene();
}


void KDrawManager::Draw_P(const VERTEX_P*  pData, const KDrawLocal& local, KPrimType type, unsigned int vertCnt)
{
	ApplyWorldMatrix(local);
	unsigned int primCnt = vertCnt / primVertMagic[type*2] - primVertMagic[type*2 + 1];
	g_pd3dDevice->SetFVF( D3DFVF_VERTEX_P );
	g_pd3dDevice->DrawPrimitiveUP(pirmTypeMap[type], primCnt, pData, sizeof(VERTEX_P));
}

void KDrawManager::Draw_PT(const VERTEX_PT*  pData, const KDrawLocal& local, KPrimType type, unsigned int vertCnt)
{
	ApplyWorldMatrix(local);
	unsigned int primCnt = vertCnt / primVertMagic[type*2] - primVertMagic[type*2 + 1];
	g_pd3dDevice->SetFVF( D3DFVF_VERTEX_PT );
	g_pd3dDevice->DrawPrimitiveUP(pirmTypeMap[type], primCnt, pData, sizeof(VERTEX_PT));
}

void KDrawManager::Draw_PC(const VERTEX_PC*  pData, const KDrawLocal& local, KPrimType type, unsigned int vertCnt)
{
	ApplyWorldMatrix(local);
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
	unsigned int primCnt = vertCnt / primVertMagic[type*2] - primVertMagic[type*2 + 1];
	g_pd3dDevice->SetFVF( D3DFVF_VERTEX_PC );
	g_pd3dDevice->DrawPrimitiveUP(pirmTypeMap[type], primCnt, pData, sizeof(VERTEX_PC));
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_CONSTANT );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_CONSTANT );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
}


void KDrawManager::SetupView(float w, float h, const KVec2& center, KColor bgClr)
{
	unsigned int d3d_color = bgClr;
	BYTE* pColor = (BYTE*)&d3d_color;
	std::swap(pColor[0], pColor[2]);
	g_bgColor = d3d_color;

	D3DXMATRIX viewMat;
	D3DXMatrixOrthoRH(&viewMat, w, h, -1, 1);

	D3DXMATRIX transMat;
	D3DXMatrixTranslation(&transMat, -center[0], -center[1], 0);

	D3DXMATRIX tempMat1;
	D3DXMatrixMultiply(&tempMat1, &transMat, &viewMat);

	g_pd3dDevice->SetTransform( D3DTS_VIEW, &tempMat1 );


	D3DXMATRIXA16 matProj;
	D3DXMatrixIdentity(&matProj);
	g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
}


void KDrawManager::DrawSprite(const KDrawLocal& local, KTexture* pTexture, const KSpriteRect& rect, KColor color)
{
	pTexture->Bind();

	ApplyConstColor(color);
	g_pd3dDevice->SetFVF( D3DFVF_VERTEX_PT );
	float hx = (float)rect.w;
	float hy = (float)rect.h;
	float u_bias = 0.5f / (float)pTexture->mWidth;
	float v_bias = 0.5f / (float)pTexture->mHeight;
	float u0 = (float)rect.sx / (float)pTexture->mWidth + u_bias;
	float u1 = (float)(rect.sx+rect.w) / (float)pTexture->mWidth + u_bias;
	float v0 = (float)rect.sy / (float)pTexture->mHeight + v_bias;
	float v1 = (float)(rect.sy+rect.h) / (float)pTexture->mHeight + v_bias;

	D3DXMATRIX transMat;
	D3DXMatrixIdentity(&transMat);
	transMat(0,0) = local.rot.mCosTheta;
	transMat(0,1) = local.rot.mSinTheta;
	transMat(3,0) = local.x;

	transMat(1,0) = -local.rot.mSinTheta;
	transMat(1,1) = local.rot.mCosTheta;
	transMat(3,1) = local.y;

	g_pd3dDevice->SetTransform( D3DTS_WORLD, &transMat );

	float drawData[] = {
		-rect.cx,   -rect.cy,   0,      u0, v1,
		hx-rect.cx, -rect.cy,   0,      u1, v1,
		-rect.cx, hy-rect.cy,   0,      u0, v0,
		hx-rect.cx, hy-rect.cy, 0,      u1, v0
	};

	g_pd3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, drawData, sizeof(VERTEX_PT));
}

void KDrawManager::GetViewportSize(unsigned int& width, unsigned int& height)
{
	width = g_viewportWidth;
	height = g_viewportHeight;
}